/*
 * ndmp_session.c  - The NDMP session management
 *
 * One NDMP session has the following main attributes
 *
 *	NDMP server - The server the session is connected to
 * 	NDMP user name and password
 *	Device - The tape or robot device open, there will be only one device per session, 
 *	if there are more than one device, multiple sessions to be started
 *	queue - The SCSI command queue 
 * 	session_id - The unique id to recognise the session
 *	NDMP thread - the threads which does all the work for the session
 *	scsi host - The SCSI host the session is associated with, there will be only one scsi device per scsi host.
 *
 * 	Cretion of one session means connecting to the NDMP server, authenticating, opening the device and then creation of the scsi host
 *
 *	The 'sessions' variable has a list of sessions which is handled by the driver.
 *
 *      Author: Aboo Valappil
 *      Date: 2/10/2008
 *      Based on: None
 */

#include "ndmp_main.h"

extern struct ndmp_scsi_sessions sessions;
extern struct scsi_host_template ndmp_scsi_host_template;
extern struct kmem_cache *ndmp_scsi_task_cache;

/* The following are for setting and getting the session states 
 * One type of these function locks the spinlock and other one do not
 */
void __ndmp_change_session_state (struct ndmp_scsi_session *session, char op, char state)
{
        if ( state & 1 ) set_bit(op, &session->state);
        else clear_bit(op,&session->state);
        if (state & 2 ) set_bit(op+1, (long *)&session->state);
        else clear_bit(op+1,&session->state);
}

void ndmp_change_session_state (struct ndmp_scsi_session *session, char op, char state)
{
        spin_lock(&session->task_lock);
        __ndmp_change_session_state(session,op,state);
        spin_unlock(&session->task_lock);
}

char __ndmp_session_state (struct ndmp_scsi_session *session, char op)
{
        char ret=0;
        if ( test_bit(op,&session->state)) ret|=1;
        if ( test_bit(op+1,&session->state)) ret|=2;
        return ret;
}

char ndmp_session_state (struct ndmp_scsi_session *session, char op)
{
        char ret=0;
        spin_lock(&session->task_lock);
        ret=__ndmp_session_state(session,op);
        spin_unlock(&session->task_lock);
        return ret;
}

/*  ndmp_is_session_empty - Check if the session is empty or not */
static int ndmp_is_session_empty(struct ndmp_scsi_session *session)
{

        struct ndmp_scsi_task *task,*tmp;

        list_for_each_entry_safe(task, tmp, &session->task_queue, queue) {
                        return 0;
        }
        return 1;
}

/* 
 * ndmp_remove_session - Removes a session 
 * This is called by the NDMP thread cleanup or the watchdog when the IO is not progressing.
 */
void ndmp_remove_session(struct ndmp_scsi_session *session)
{

        struct Scsi_Host *shost;
        struct ndmp_scsi_task *task,*tmp;

        shost=session->shost;


	spin_lock(&session->task_lock);
        if ( ! ndmp_is_session_empty(session) ) {
               	printk("ndmphba:%d: Session is not empty, aborting all commands\n", session->session_id);
		// Destroys the tasks in the queue first, the commands are completed with not ready and communication failure sense code
        	list_for_each_entry_safe(task, tmp, &session->task_queue, queue) {
               		printk("ndmphba:%d: aborting command: %d\n", session->session_id, task->seq_number);
			task->sc->result = DID_NO_CONNECT << 16;
			scsi_set_resid(task->sc, scsi_bufflen(task->sc));
			ndmp_set_sensedata_commfailure(task->sc);
			ndmp_scsi_complete_task(task);
		}
        }
        session->ndmp_task=NULL;
	spin_unlock(&session->task_lock);

// Remove the session from the sessions queue.
        list_del(&session->list);

// Remove the SCSI host
        scsi_remove_host(shost);
        scsi_host_put(shost);

// Destroy the sysfs object
	ndmp_destroy_session_kobj(session);

// Reduce the module count by one as the session is removed.
        module_put(THIS_MODULE);
        printk("ndmphba: Removed session: %d\n", session->session_id);
}

/* ndmp_scsi_destroy_session - Destroys a session, just a matter of killing the NDMP thread, rest will be done by the NDMP thread cleanup 
 * This is called by the IOCTL to remove a session
 */
void ndmp_scsi_destroy_session(struct ndmp_scsi_session *session)
{

	if ( ! kill_ndmp_thread(session)) ndmp_remove_session(session);

}

/* get_ndmp_session - Get a session with the specified session id from the session queue */
struct ndmp_scsi_session  *get_ndmp_session(int session_id)
{
        struct ndmp_scsi_session *session, *tmp;

        list_for_each_entry_safe(session, tmp, &sessions.session_queue, list) {
                if (session->session_id == session_id ) {
                        return session;
                }

        }
        return NULL;
}

/* ndmp_scsi_create - The routine to create a session. This is called by the IOCTL to create a session 
 *
 *	This initializes all the session attributes, creates scsi host, initializes queues, initializes the watchdog time,
 * 	and then starts the NDMP thread which will do rest of the work for the session
*/
struct ndmp_scsi_session *ndmp_scsi_create_session(char *ndmp_server, char *username, char *password, char *scsi_device, char type)
{
        struct Scsi_Host *shost;
        struct ndmp_scsi_session *session;
        int rc;

        printk("ndmphba: Creating new NDMP session: %d\n", sessions.last_id+1);

// Allocates the scsi_host structure and initializes the values
        shost = scsi_host_alloc(&ndmp_scsi_host_template, sizeof(*session));

        if (!shost) {
                session=NULL;
                goto last;
        }

        session = (struct ndmp_scsi_session *)shost->hostdata;
        sessions.last_id++;
        memset(session, 0, sizeof(*session));

        INIT_LIST_HEAD(&session->task_queue);
        session->seq_number=1;
        session->old_seq_number=1;

        session->rmh=0;

        shost->max_id = NDMP_SCSI_MAX_TARGETS;
        shost->max_lun = NDMP_SCSI_MAX_LUNS;
        shost->max_channel = NDMP_SCSI_MAX_CHANNELS;
        shost->max_cmd_len = NDMP_SCSI_MAX_CMD_LEN;

        session->shost=shost;
        INIT_LIST_HEAD(&session->list);
        spin_lock_init(&session->task_lock);

// Add the scsi host for this session
        rc = scsi_add_host(shost, NULL);

        if (rc) {
                printk("ndmphba: Adding SCSI HBA failed\n");
                scsi_host_put(shost);
                session=NULL;
                goto last;
        }

        printk( "ndmphba: Added new SCSI HBA in session: %d\n", sessions.last_id);

// Copy the NDMP session parameters passed by the IOCTL routine, this will be used by NDMP thread to start session
        strncpy(session->ndmp_server,ndmp_server,16);
        strncpy(session->username,username,16);
        strncpy(session->password,password,16);
        strncpy(session->scsi_device,scsi_device,16);
        session->last_fragment=1;
        session->device_type=type;
	session->tcpwnd=shost->max_sectors * 512 + 2000;

// Initialize the wait queue, this is wait queue used by NDMP thread waiting for the tasks
        init_waitqueue_head (&session->txq);

// Initialize the watchdog timer. 
        init_timer(&session->watchdog_timer);
        session->watchdog_timer.data = (unsigned long)session;
        session->watchdog_timer.function = ndmp_scsi_watchdog;
        session->watchdog_timer.expires = NDMP_HBA_TIMEOUT;
        add_timer(&session->watchdog_timer);

        ndmp_change_session_state(session, HBA_STATE_CONNECTION, _NDMP_STAT_NR);

// Assign session_id and add this session to the sessions queue
        session->session_id=sessions.last_id;
        list_add_tail(&session->list, &(sessions.session_queue));

// Creates the session sysfs objects
	ndmp_create_session_kobj(session);

// Start the NDMP thread which will start and do all the work for this session
        session->ndmp_task = kthread_run(ndmp_thread,session,"ndmp-thread");

// Incriment the module ny one, do not let the module to be unloaded if a session exist
        try_module_get(THIS_MODULE);


last:
        return session;

}

/*
 * ndmp_scsi_alloc_task - Allocate the task from the slab allocator
 * This also initializes some values of the task
 */
struct ndmp_scsi_task *ndmp_scsi_alloc_task(struct ndmp_scsi_session *session)
{
        struct ndmp_scsi_task *task;

        task = kmem_cache_alloc(ndmp_scsi_task_cache, GFP_ATOMIC);
        if (!task)  return NULL;
        memset(task, 0, sizeof(*task));
        task->flags = NDMP_SCSI_TASK_EMPTY;
        INIT_LIST_HEAD(&task->queue);
        task->sc = NULL;
        task->session = session;
        task->seq_number=++session->seq_number;

        return task;
}
